/*
 * oid2name，一个PostgreSQL应用程序，用于将文件系统上的OID映射到表和数据库名称。
 *
 * 最初由
 * B. Palmer, bpalmer@crimelabs.net 2001年1月17日
 *
 * contrib/oid2name/oid2name.c
 */
#include "postgres_fe.h"

#include "catalog/pg_class_d.h"
#include "common/connect.h"
#include "common/logging.h"
#include "common/string.h"
#include "getopt_long.h"
#include "libpq-fe.h"
#include "pg_getopt.h"

/* 一个可扩展的数组，用于跟踪要显示的元素 */
typedef struct
{
	char	  **array;
	int			num;
	int			alloc;
} eary;

/* 这些是命令行参数的opts结构 */
struct options
{
	eary	   *tables;
	eary	   *oids;
	eary	   *filenodes;

	bool		quiet;
	bool		systables;
	bool		indexes;
	bool		nodb;
	bool		extended;
	bool		tablespaces;

	char	   *dbname;
	char	   *hostname;
	char	   *port;
	char	   *username;
	const char *progname;
};

/* 函数原型 */
static void fc_help(const char *fc_progname);
void		get_opts(int, char **, struct options *);
void		add_one_elt(char *fc_eltname, eary *fc_eary);
char	   *get_comma_elts(eary *fc_eary);
PGconn	   *sql_conn(struct options *);
int			sql_exec(PGconn *, const char *fc_sql, bool fc_quiet);
void		sql_exec_dumpalldbs(PGconn *, struct options *);
void		sql_exec_dumpalltables(PGconn *, struct options *);
void		sql_exec_searchtables(PGconn *, struct options *);
void		sql_exec_dumpalltbspc(PGconn *, struct options *);

/* 函数用于解析命令行选项并检查一些使用错误。 */
void get_opts(int fc_argc, char **fc_argv, struct options *fc_my_opts)
{
	static struct option fc_long_options[] = {
		{"dbname", required_argument, NULL, 'd'},
		{"host", required_argument, NULL, 'h'},
		{"host", required_argument, NULL, 'H'}, /* 已弃用 */
		{"filenode", required_argument, NULL, 'f'},
		{"indexes", no_argument, NULL, 'i'},
		{"oid", required_argument, NULL, 'o'},
		{"port", required_argument, NULL, 'p'},
		{"quiet", no_argument, NULL, 'q'},
		{"tablespaces", no_argument, NULL, 's'},
		{"system-objects", no_argument, NULL, 'S'},
		{"table", required_argument, NULL, 't'},
		{"username", required_argument, NULL, 'U'},
		{"version", no_argument, NULL, 'V'},
		{"extended", no_argument, NULL, 'x'},
		{"help", no_argument, NULL, '?'},
		{NULL, 0, NULL, 0}
	};

	int			fc_c;
	const char *fc_progname;
	int			fc_optindex;

	pg_logging_init(fc_argv[0]);
	fc_progname = get_progname(fc_argv[0]);

	/* 设置默认值 */
	fc_my_opts->quiet = false;
	fc_my_opts->systables = false;
	fc_my_opts->indexes = false;
	fc_my_opts->nodb = false;
	fc_my_opts->extended = false;
	fc_my_opts->tablespaces = false;
	fc_my_opts->dbname = NULL;
	fc_my_opts->hostname = NULL;
	fc_my_opts->port = NULL;
	fc_my_opts->username = NULL;
	fc_my_opts->progname = fc_progname;

	if (fc_argc > 1)
	{
		if (strcmp(fc_argv[1], "--help") == 0 || strcmp(fc_argv[1], "-?") == 0)
		{
			fc_help(fc_progname);
			exit(0);
		}
		if (strcmp(fc_argv[1], "--version") == 0 || strcmp(fc_argv[1], "-V") == 0)
		{
			puts("oid2name (PostgreSQL) " PG_VERSION);
			exit(0);
		}
	}

	/* 获取选项 */
	while ((fc_c = getopt_long(fc_argc, fc_argv, "d:f:h:H:io:p:qsSt:U:x", fc_long_options, &fc_optindex)) != -1)
	{
		switch (fc_c)
		{
				/* 指定数据库 */
			case 'd':
				fc_my_opts->dbname = pg_strdup(optarg);
				break;

				/* 指定一个要显示的filenode */
			case 'f':
				add_one_elt(optarg, fc_my_opts->filenodes);
				break;

				/* 要连接的主机 */
			case 'H':			/* 已弃用 */
			case 'h':
				fc_my_opts->hostname = pg_strdup(optarg);
				break;

				/* 也显示索引 */
			case 'i':
				fc_my_opts->indexes = true;
				break;

				/* 指定一个要显示的Oid */
			case 'o':
				add_one_elt(optarg, fc_my_opts->oids);
				break;

				/* 远程主机上要连接的端口 */
			case 'p':
				fc_my_opts->port = pg_strdup(optarg);
				break;

				/* 不显示头部 */
			case 'q':
				fc_my_opts->quiet = true;
				break;

				/* 仅转储表空间 */
			case 's':
				fc_my_opts->tablespaces = true;
				break;

				/* 显示系统表 */
			case 'S':
				fc_my_opts->systables = true;
				break;

				/* 指定一个要显示的表名 */
			case 't':
				add_one_elt(optarg, fc_my_opts->tables);
				break;

				/* 用户名 */
			case 'U':
				fc_my_opts->username = pg_strdup(optarg);
				break;

				/* 显示额外列 */
			case 'x':
				fc_my_opts->extended = true;
				break;

			default:
				/* getopt_long already emitted a complaint */
				pg_log_error_hint("Try \"%s --help\" for more information.", fc_progname);
				exit(1);
		}
	}

	if (optind < fc_argc)
	{
		pg_log_error("too many command-line arguments (first is \"%s\")",
					 fc_argv[optind]);
		pg_log_error_hint("Try \"%s --help\" for more information.", fc_progname);
		exit(1);
	}
}

static void fc_help(const char *fc_progname)
{
	printf("%s helps examining the file structure used by PostgreSQL.\n\n"
		   "Usage:\n"
		   "  %s [OPTION]...\n"
		   "\nOptions:\n"
		   "  -f, --filenode=FILENODE    show info for table with given file node\n"
		   "  -i, --indexes              show indexes and sequences too\n"
		   "  -o, --oid=OID              show info for table with given OID\n"
		   "  -q, --quiet                quiet (don't show headers)\n"
		   "  -s, --tablespaces          show all tablespaces\n"
		   "  -S, --system-objects       show system objects too\n"
		   "  -t, --table=TABLE          show info for named table\n"
		   "  -V, --version              output version information, then exit\n"
		   "  -x, --extended             extended (show additional columns)\n"
		   "  -?, --help                 show this help, then exit\n"
		   "\nConnection options:\n"
		   "  -d, --dbname=DBNAME        database to connect to\n"
		   "  -h, --host=HOSTNAME        database server host or socket directory\n"
		   "  -H                         same as -h, deprecated option\n"
		   "  -p, --port=PORT            database server port number\n"
		   "  -U, --username=USERNAME    connect as specified database user\n"
		   "\nThe default action is to show all database OIDs.\n\n"
		   "Report bugs to <%s>.\n"
		   "%s home page: <%s>\n",
		   fc_progname, fc_progname, PACKAGE_BUGREPORT, PACKAGE_NAME, PACKAGE_URL);
}

/*
 * add_one_elt
 *
 * 向一个（可能为空的）eary结构添加一个元素。
 */
void add_one_elt(char *fc_eltname, eary *fc_eary)
{
	if (fc_eary->alloc == 0)
	{
		fc_eary	  ->alloc = 8;
		fc_eary	  ->array = (char **) pg_malloc(8 * sizeof(char *));
	}
	else if (fc_eary->num >= fc_eary->alloc)
	{
		fc_eary	  ->alloc *= 2;
		fc_eary	  ->array = (char **) pg_realloc(fc_eary->array,
												 fc_eary->alloc * sizeof(char *));
	}

	fc_eary	  ->array[fc_eary->num] = pg_strdup(fc_eltname);
	fc_eary	  ->num++;
}

/*
 * get_comma_elts
 *
 * 返回eary的元素作为（新分配的）单个字符串，字符串用单引号括起来，
 * 以逗号分隔并为插入SQL语句进行正确转义。
 */
char * get_comma_elts(eary *fc_eary)
{
	char	   *fc_ret,
			   *fc_ptr;
	int			fc_i,
				fc_length = 0;

	if (fc_eary->num == 0)
		return pg_strdup("");

	/*
	 * PQescapeString需要2 * length + 1个字节的额外空间。每个元素添加两个字符
	 * 用于单引号，逗号添加一个。
	 */
	for (fc_i = 0; fc_i < fc_eary->num; fc_i++)
		fc_length += strlen(fc_eary->array[fc_i]);

	fc_ret = (char *) pg_malloc(fc_length * 2 + 4 * fc_eary->num);
	fc_ptr = fc_ret;

	for (fc_i = 0; fc_i < fc_eary->num; fc_i++)
	{
		if (fc_i != 0)
			sprintf(fc_ptr++, ",");
		sprintf(fc_ptr++, "'");
		fc_ptr += PQescapeString(fc_ptr, fc_eary->array[fc_i], strlen(fc_eary->array[fc_i]));
		sprintf(fc_ptr++, "'");
	}

	return fc_ret;
}

/* 建立与数据库的连接。 */
PGconn * sql_conn(struct options *fc_my_opts)
{
	PGconn	   *fc_conn;
	char	   *fc_password = NULL;
	bool		fc_new_pass;
	PGresult   *fc_res;

	/*
	 * 开始连接。循环直到我们获得后端请求的密码。
	 */
	do
	{
#define PARAMS_ARRAY_SIZE	7

		const char *fc_keywords[PARAMS_ARRAY_SIZE];
		const char *fc_values[PARAMS_ARRAY_SIZE];

		fc_keywords[0] = "host";
		fc_values[0] = fc_my_opts->hostname;
		fc_keywords[1] = "port";
		fc_values[1] = fc_my_opts->port;
		fc_keywords[2] = "user";
		fc_values[2] = fc_my_opts->username;
		fc_keywords[3] = "password";
		fc_values[3] = fc_password;
		fc_keywords[4] = "dbname";
		fc_values[4] = fc_my_opts->dbname;
		fc_keywords[5] = "fallback_application_name";
		fc_values[5] = fc_my_opts->progname;
		fc_keywords[6] = NULL;
		fc_values[6] = NULL;

		fc_new_pass = false;
		fc_conn = PQconnectdbParams(fc_keywords, fc_values, true);

		if (!fc_conn)
			pg_fatal("could not connect to database %s",
					 fc_my_opts->dbname);

		if (PQstatus(fc_conn) == CONNECTION_BAD &&
			PQconnectionNeedsPassword(fc_conn) &&
			!fc_password)
		{
			PQfinish(fc_conn);
			fc_password = simple_prompt("Password: ", false);
			fc_new_pass = true;
		}
	} while (fc_new_pass);

	/* 检查以确保后端连接成功建立 */
	if (PQstatus(fc_conn) == CONNECTION_BAD)
	{
		pg_log_error("%s", PQerrorMessage(fc_conn));
		PQfinish(fc_conn);
		exit(1);
	}

	fc_res = PQexec(fc_conn, ALWAYS_SECURE_SEARCH_PATH_SQL);
	if (PQresultStatus(fc_res) != PGRES_TUPLES_OK)
	{
		pg_log_error("could not clear search_path: %s",
					 PQerrorMessage(fc_conn));
		PQclear(fc_res);
		PQfinish(fc_conn);
		exit(1);
	}
	PQclear(fc_res);

	/* 如果良好，则返回连接 */
	return fc_conn;
}

/*
 * 实际代码用于调用数据库并打印输出数据。
 */
int sql_exec(PGconn *fc_conn, const char *fc_todo, bool fc_quiet)
{
	PGresult   *fc_res;

	int			fc_nfields;
	int			fc_nrows;
	int			fc_i,
				fc_j,
				fc_l;
	int		   *fc_length;
	char	   *fc_pad;

	/* 发出调用 */
	fc_res = PQexec(fc_conn, fc_todo);

	/* 检查并处理错误 */
	if (!fc_res || PQresultStatus(fc_res) > 2)
	{
		pg_log_error("query failed: %s", PQerrorMessage(fc_conn));
		pg_log_error_detail("Query was: %s", fc_todo);

		PQclear(fc_res);
		PQfinish(fc_conn);
		exit(1);
	}

	/* 获取字段数量 */
	fc_nrows = PQntuples(fc_res);
	fc_nfields = PQnfields(fc_res);

	/* 对于每个字段，获取所需的宽度 */
	fc_length = (int *) pg_malloc(sizeof(int) * fc_nfields);
	for (fc_j = 0; fc_j < fc_nfields; fc_j++)
		fc_length[fc_j] = strlen(PQfname(fc_res, fc_j));

	for (fc_i = 0; fc_i < fc_nrows; fc_i++)
	{
		for (fc_j = 0; fc_j < fc_nfields; fc_j++)
		{
			fc_l = strlen(PQgetvalue(fc_res, fc_i, fc_j));
			if (fc_l > fc_length[fc_j])
				fc_length[fc_j] = strlen(PQgetvalue(fc_res, fc_i, fc_j));
		}
	}

	/* 打印标题 */
	if (!fc_quiet)
	{
		for (fc_j = 0, fc_l = 0; fc_j < fc_nfields; fc_j++)
		{
			fprintf(stdout, "%*s", fc_length[fc_j] + 2, PQfname(fc_res, fc_j));
			fc_l += fc_length[fc_j] + 2;
		}
		fprintf(stdout, "\n");
		fc_pad = (char *) pg_malloc(fc_l + 1);
		MemSet(fc_pad, '-', fc_l);
		fc_pad[fc_l] = '\0';
		fprintf(stdout, "%s\n", fc_pad);
		free(fc_pad);
	}

	/* 对于每一行，转储信息 */
	for (fc_i = 0; fc_i < fc_nrows; fc_i++)
	{
		for (fc_j = 0; fc_j < fc_nfields; fc_j++)
			fprintf(stdout, "%*s", fc_length[fc_j] + 2, PQgetvalue(fc_res, fc_i, fc_j));
		fprintf(stdout, "\n");
	}

	/* 清理 */
	PQclear(fc_res);
	free(fc_length);

	return 0;
}

/*
 * 转储所有数据库。没有系统对象需要担心。
 */
void sql_exec_dumpalldbs(PGconn *fc_conn, struct options *fc_opts)
{
	char		fc_todo[1024];

	/* 从系统pg_database表获取oid和数据库名称 */
	snprintf(fc_todo, sizeof(fc_todo),
			 "SELECT d.oid AS \"Oid\", datname AS \"Database Name\", "
			 "spcname AS \"Tablespace\" FROM pg_catalog.pg_database d JOIN pg_catalog.pg_tablespace t ON "
			 "(dattablespace = t.oid) ORDER BY 2");

	sql_exec(fc_conn, fc_todo, fc_opts->quiet);
}

/*
 * 转储当前数据库中的所有表、索引和序列。
 */
void sql_exec_dumpalltables(PGconn *fc_conn, struct options *fc_opts)
{
	char		fc_todo[1024];
	char	   *fc_addfields = ",c.oid AS \"Oid\", nspname AS \"Schema\", spcname as \"Tablespace\" ";

	snprintf(fc_todo, sizeof(fc_todo),
			 "SELECT pg_catalog.pg_relation_filenode(c.oid) as \"Filenode\", relname as \"Table Name\" %s "
			 "FROM pg_catalog.pg_class c "
			 "	LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "
			 "	LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database(),"
			 "	pg_catalog.pg_tablespace t "
			 "WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ","
			 CppAsString2(RELKIND_MATVIEW) "%s%s) AND "
			 "	%s"
			 "		t.oid = CASE"
			 "			WHEN reltablespace <> 0 THEN reltablespace"
			 "			ELSE dattablespace"
			 "		END "
			 "ORDER BY relname",
			 fc_opts->extended ? fc_addfields : "",
			 fc_opts->indexes ? "," CppAsString2(RELKIND_INDEX) "," CppAsString2(RELKIND_SEQUENCE) : "",
			 fc_opts->systables ? "," CppAsString2(RELKIND_TOASTVALUE) : "",
			 fc_opts->systables ? "" : "n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname !~ '^pg_toast' AND");

	sql_exec(fc_conn, fc_todo, fc_opts->quiet);
}

/*
 * 显示当前数据库中每个给定对象的oid、filenode、名称、模式和表空间。
 */
void sql_exec_searchtables(PGconn *fc_conn, struct options *fc_opts)
{
	char	   *fc_todo;
	char	   *fc_qualifiers,
			   *fc_ptr;
	char	   *fc_comma_oids,
			   *fc_comma_filenodes,
			   *fc_comma_tables;
	bool		fc_written = false;
	char	   *fc_addfields = ",c.oid AS \"Oid\", nspname AS \"Schema\", spcname as \"Tablespace\" ";

	/* 获取表的限定符，无论是名称、filenodes还是OID */
	fc_comma_oids = get_comma_elts(fc_opts->oids);
	fc_comma_tables = get_comma_elts(fc_opts->tables);
	fc_comma_filenodes = get_comma_elts(fc_opts->filenodes);

	/* 80个额外字符用于SQL表达式 */
	fc_qualifiers = (char *) pg_malloc(strlen(fc_comma_oids) + strlen(fc_comma_tables) +
									strlen(fc_comma_filenodes) + 80);
	fc_ptr = fc_qualifiers;

	if (fc_opts->oids->num > 0)
	{
		fc_ptr += sprintf(fc_ptr, "c.oid IN (%s)", fc_comma_oids);
		fc_written = true;
	}
	if (fc_opts->filenodes->num > 0)
	{
		if (fc_written)
			fc_ptr += sprintf(fc_ptr, " OR ");
		fc_ptr += sprintf(fc_ptr, "pg_catalog.pg_relation_filenode(c.oid) IN (%s)", fc_comma_filenodes);
		fc_written = true;
	}
	if (fc_opts->tables->num > 0)
	{
		if (fc_written)
			fc_ptr += sprintf(fc_ptr, " OR ");
		sprintf(fc_ptr, "c.relname ~~ ANY (ARRAY[%s])", fc_comma_tables);
	}
	free(fc_comma_oids);
	free(fc_comma_tables);
	free(fc_comma_filenodes);

	/* 现在构建查询 */
	fc_todo = psprintf("SELECT pg_catalog.pg_relation_filenode(c.oid) as \"Filenode\", relname as \"Table Name\" %s\n"
					"FROM pg_catalog.pg_class c\n"
					"	LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
					"	LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database(),\n"
					"	pg_catalog.pg_tablespace t\n"
					"WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ","
					CppAsString2(RELKIND_MATVIEW) ","
					CppAsString2(RELKIND_INDEX) ","
					CppAsString2(RELKIND_SEQUENCE) ","
					CppAsString2(RELKIND_TOASTVALUE) ") AND\n"
					"		t.oid = CASE\n"
					"			WHEN reltablespace <> 0 THEN reltablespace\n"
					"			ELSE dattablespace\n"
					"		END AND\n"
					"  (%s)\n"
					"ORDER BY relname\n",
					fc_opts->extended ? fc_addfields : "",
					fc_qualifiers);

	free(fc_qualifiers);

	sql_exec(fc_conn, fc_todo, fc_opts->quiet);
}

void sql_exec_dumpalltbspc(PGconn *fc_conn, struct options *fc_opts)
{
	char		fc_todo[1024];

	snprintf(fc_todo, sizeof(fc_todo),
			 "SELECT oid AS \"Oid\", spcname as \"Tablespace Name\"\n"
			 "FROM pg_catalog.pg_tablespace");

	sql_exec(fc_conn, fc_todo, fc_opts->quiet);
}

int main(int fc_argc, char **fc_argv)
{
	struct options *fc_my_opts;
	PGconn	   *fc_pgconn;

	fc_my_opts = (struct options *) pg_malloc(sizeof(struct options));

	fc_my_opts->oids = (eary *) pg_malloc(sizeof(eary));
	fc_my_opts->tables = (eary *) pg_malloc(sizeof(eary));
	fc_my_opts->filenodes = (eary *) pg_malloc(sizeof(eary));

	fc_my_opts->oids->num = fc_my_opts->oids->alloc = 0;
	fc_my_opts->tables->num = fc_my_opts->tables->alloc = 0;
	fc_my_opts->filenodes->num = fc_my_opts->filenodes->alloc = 0;

	/* 解析选项 */
	get_opts(fc_argc, fc_argv, fc_my_opts);

	if (fc_my_opts->dbname == NULL)
	{
		fc_my_opts->dbname = "postgres";
		fc_my_opts->nodb = true;
	}
	fc_pgconn = sql_conn(fc_my_opts);

	/* 仅显示表空间 */
	if (fc_my_opts->tablespaces)
	{
		if (!fc_my_opts->quiet)
			printf("All tablespaces:\n");
		sql_exec_dumpalltbspc(fc_pgconn, fc_my_opts);

		PQfinish(fc_pgconn);
		exit(0);
	}

	/* 在数据库中显示给定元素 */
	if (fc_my_opts->oids->num > 0 ||
		fc_my_opts->tables->num > 0 ||
		fc_my_opts->filenodes->num > 0)
	{
		if (!fc_my_opts->quiet)
			printf("From database \"%s\":\n", fc_my_opts->dbname);
		sql_exec_searchtables(fc_pgconn, fc_my_opts);

		PQfinish(fc_pgconn);
		exit(0);
	}

	/* 未给定元素；转储给定数据库 */
	if (fc_my_opts->dbname && !fc_my_opts->nodb)
	{
		if (!fc_my_opts->quiet)
			printf("From database \"%s\":\n", fc_my_opts->dbname);
		sql_exec_dumpalltables(fc_pgconn, fc_my_opts);

		PQfinish(fc_pgconn);
		exit(0);
	}

	/* 也没有数据库；转储所有数据库 */
	if (!fc_my_opts->quiet)
		printf("All databases:\n");
	sql_exec_dumpalldbs(fc_pgconn, fc_my_opts);

	PQfinish(fc_pgconn);
	return 0;
}
